84장. CloudWatch Logs와 Logs Insights
이 장에서 말하고자 하는 것
메트릭이 “숫자” 라면 로그는 “이야기” 다.
- 무슨 요청이 들어왔는지
- 어디서 에러가 났는지
- 어떤 사용자가 무엇을 했는지
이걸 수집하고 검색하는 도구가
Amazon CloudWatch Logs
이고, 그 안에서 빠르게 질의하는 도구가
CloudWatch Logs Insights
다.
1. 기본 단위 — Log Group · Log Stream · Event
Log Group "/ecs/orders"
├─ Log Stream app/task-1
│ ├─ event: "request received ..."
│ ├─ event: "DB query ..."
│ └─ ...
├─ Log Stream app/task-2
└─ ...
- Log Group — 서비스 · 환경 단위 묶음
- Log Stream — 보통 한 컨테이너 / 인스턴스 단위
- Event — 로그 한 줄
2. 로그 보관 기간 — 비용의 가장 큰 부분
기본은 영원히 보관이다.
한 달 100 GB → 영원히 → 매달 비용이 누적
거의 항상
운영 로그: 30일 ~ 90일
감사 로그: 365일+ (S3로 아카이브)
디버그 로그: 7일
Log Group 만들 때 retention 을 같이 정한다
3. 구조화 로그 (Structured Logging)
평문 로그는 검색이 어렵다.
"POST /api/orders 200 in 184ms for user u-1"
JSON 한 줄로 바꾸면
{ "method":"POST", "path":"/api/orders", "status":200,
"duration_ms":184, "user_id":"u-1" }
CloudWatch Logs Insights는 JSON 필드를 자동으로 인식한다.
운영 로그는 무조건 JSON. 처음부터 그렇게 만든다
4. CloudWatch Logs Insights — 빠른 질의 언어
SQL 비슷한 별도의 쿼리 언어.
fields @timestamp, status, duration_ms, user_id
| filter status >= 500
| stats count() by bin(1m)
fields @timestamp, path, duration_ms
| filter duration_ms > 1000
| sort duration_ms desc
| limit 20
운영 상황에서 가장 자주 쓰는 도구다.
“방금 5xx 가 늘었는데 어떤 경로?” 같은 질문에 분 단위로 답한다.
5. Metric Filter — 로그에서 메트릭 만들기
로그를 보면서 메트릭을 자동 생성할 수 있다.
Filter Pattern: { $.status >= 500 }
↓ 매칭될 때마다
Metric: MyApp/ServerErrors +1
이 메트릭에 알람을 걸면 “로그 기반 알람” 이 만들어진다.
EMF를 쓰면 Metric Filter 없이도 메트릭이 자동 생성됨
6. Subscription Filter — 다른 곳으로 흘려보내기
로그를 다른 시스템으로 실시간 전송.
CloudWatch Logs → Kinesis Data Firehose → S3 / OpenSearch
→ Lambda (실시간 처리)
→ 다른 계정의 Logs
조직 차원에서 로그를 한 데이터 레이크로 모을 때 사용.
7. 어떤 로그를 보내야 하는가
애플리케이션 로그
- HTTP 요청 한 줄 (path · status · duration · user_id · trace_id)
- 비즈니스 이벤트 (주문 생성 · 결제 성공)
- 에러 (stack trace · 컨텍스트)
AWS 서비스 로그
- ALB 액세스 로그 (S3 → Athena 또는 직접 CloudWatch)
- CloudFront 액세스 로그
- VPC Flow Logs
- API Gateway 로그
- WAF 로그
시스템 로그
- OS / 컨테이너 syslog (필요 시)
8. 비밀이 로그에 들어가지 않게
로그는 사고가 자주 나는 자리다.
실수로 console.log(config) → DB 비밀번호가 CloudWatch에
방어:
- 로깅 라이브러리에 마스킹 설정 (
password,token같은 키) - PII (이름 · 이메일 · 신용카드 마지막 4자리) 도 정책에 따라 마스킹
- 비밀이 들어간 로그 그룹은 삭제 + 사용자 통보 가 표준
9. 우리 서비스에서
Log Group:
/ecs/orders (retention 30d, JSON, EMF)
/ecs/users (retention 30d)
/ecs/payments (retention 90d, 감사 강화)
/aws/apigw/main (retention 30d)
/aws/lambda/thumbnail (retention 14d)
ALB · CloudFront 액세스 로그: S3 → Athena
장기 보관(법적):
Kinesis Firehose → S3 (Glacier)
- 모두 JSON 구조화 로그
- 비밀번호 · 카드 번호는 마스킹
- 핵심 필드: timestamp · level · trace_id · user_id · request_id
10. 직접 확인해보기 — Logs Insights 쿼리 예
최근 5xx 분포
fields @timestamp, status, path
| filter status >= 500
| stats count() by status, path
| sort count() desc
느린 요청 Top 20
fields @timestamp, path, duration_ms
| filter duration_ms > 1000
| sort duration_ms desc
| limit 20
특정 사용자의 모든 요청
fields @timestamp, path, status
| filter user_id = "u-1"
| sort @timestamp desc
trace_id로 한 요청의 전체 흐름
fields @timestamp, @message
| filter trace_id = "abc123"
| sort @timestamp asc
11. 코드로는 이렇게 생겼다 — Terraform
resource "aws_cloudwatch_log_group" "orders" {
name = "/ecs/orders"
retention_in_days = 30
kms_key_id = aws_kms_key.logs.arn
}
resource "aws_cloudwatch_log_metric_filter" "orders_5xx" {
name = "orders-5xx"
log_group_name = aws_cloudwatch_log_group.orders.name
pattern = "{ $.status >= 500 }"
metric_transformation {
name = "ServerErrors"
namespace = "MyApp/Orders"
value = "1"
unit = "Count"
}
}
이 한 묶음이 로그 → 메트릭 → 알람 흐름의 시작.
12. 이렇게 쓰면 망한다 — 안티패턴
안티패턴 1. retention을 안 정한다
청구서가 매달 늘어난다.
안티패턴 2. 비구조화 로그
“문자열 매칭” 으로 검색하니 느리고 부정확.
처음부터 JSON
안티패턴 3. 비밀이 로그에 들어간다
로깅 라이브러리 단계에서 마스킹 — 코드에서 매번 처리하지 않는다.
안티패턴 4. 모든 로그를 한 그룹에
권한 · 보관 기간 · 알람이 다 섞인다.
서비스 · 환경별 분리
13. 한 줄로 정리
CloudWatch Logs는 운영의 이야기를 모으는 곳이며,
JSON 구조화 + 적절한 retention + Logs Insights 가 핵심이다
14. 이 장의 핵심 정리
- Log Group · Stream · Event 가 기본 단위.
- retention은 거의 항상 명시한다 — 비용 통제.
- JSON 구조화 로그가 운영의 기본.
- Logs Insights로 분 단위 질의가 가능하다.
- Metric Filter / EMF로 로그를 메트릭으로 만든다.
- 비밀과 PII가 로그에 흘러가지 않게 마스킹은 필수.